#include "q_shared.h"
#include "bg_lua.h"
#include "bg_public.h"

enum
{
	LUATYPE_INTEGER,
	LUATYPE_CLASSREF,	// Integer index into the classes list
	LUATYPE_OBJECTREF,
	LUATYPE_VECTOR3,
	LUATYPE_VECTOR4,
	LUATYPE_FLOAT,
	LUATYPE_SHORT,
	LUATYPE_BYTE,
	LUATYPE_STRING,

	LUATYPE_NORMALIZED_ANGLE,	// 16 bits
	LUATYPE_NORMALIZED_ROTATION,	// 10/11/11 compressed

	LUATYPE_COUNT,
};

typedef struct
{
	const char *name;
	int slotCount;	// Negative values = width
	const char *cl;
} luaDataTypeInfo_t;

static const luaDataTypeInfo_t luaDataTypeInfo[] =
{
	{ "INTEGER", 1, NULL },
	{ "VECTOR3", 3, "vector3" },
	{ "VECTOR4", 4, "vector4" },
	{ "FLOAT", 1, NULL },
	{ "SHORT", -16, NULL },
	{ "BYTE", -8, NULL },
	{ "NORMALIZED_ANGLE", -16, NULL },
	{ "NORMALIZED_ROTATION", 1, "Vector4" }
};

// Destination table is 2, source table is 3
int BG_LuaMergeTable(lua_State *L, qboolean aggressive)
{
	lua_pushnil(L);
	// nil
	while(lua_next(L, 3) != 0)
	{
		// k / src[k]
		if(!aggressive)
		{
			// Skip this value if it's already in the destination
			// and merging isn't aggressive

			lua_pushvalue(L, -2);
			// k / src[k] / k
			lua_gettable(L, 2);
			// k / src[k] / dest[k]
			if(!lua_isnil(L, -1))
			{
				lua_pop(L, 2);
				// k
				continue;
			}
			lua_pop(L, 1);
		}
		// k / src[k]

		lua_pushvalue(L, -2);
		// k / src[k] / k
		lua_pushvalue(L, -2);
		// k / src[k] / k / src[k]
		lua_settable(L, 2);
		// k / src[k]

		lua_pop(L, 1);
		// k
	}
	// empty

	return 0;
}

// 2 is the string
int BG_LuaParseVector3(lua_State *L)
{
	const char *str;
	float v[3];

	lua_pushvalue(L, 2);
	// str
	str = luaL_checkstring(L, -1);
	sscanf(str, "%f %f %f", &v[0], &v[1], &v[2]);

	lua_pop(L, 1);
	// empty

	lua_pushnumber(L, v[0]);
	lua_pushnumber(L, v[1]);
	lua_pushnumber(L, v[2]);

	return 3;
}

// 2 is table, 3 is metatable
int BG_LuaSetMetaTable(lua_State *L)
{
	lua_pushvalue(L, 3);
	// MT
	lua_setmetatable(L, 2);
	// empty
	return 0;
}

// 2 is the string
int BG_LuaTrace(lua_State *L)
{
	const char *str;
	float v[3];

	lua_pushvalue(L, 2);
	// str
	str = luaL_checkstring(L, -1);
	Com_Printf("%s", str);

	lua_pop(L, 1);
	// empty

	return 0;
}

// 2 is the table, 3 is the key, 4 is the value
int BG_LuaRawSet(lua_State *L)
{
	if(lua_type(L, 2) != LUA_TTABLE)
	{
		lua_pushstring(L, "RawSet called on non-table");
		lua_error(L);
	}
	lua_pushvalue(L, 3);
	// k
	lua_pushvalue(L, 4);
	// k / v
	lua_rawset(L, 2);
	// empty

	return 0;
}


// 2 is the table, 3 is the key
int BG_LuaRawGet(lua_State *L)
{
	if(lua_type(L, 2) != LUA_TTABLE)
	{
		lua_pushstring(L, "RawGet called on non-table");
		lua_error(L);
	}
	lua_pushvalue(L, 3);
	// k
	lua_rawget(L, 2);
	// v

	return 1;
}

int BG_LuaError(lua_State *L)
{
	lua_error(L);
	return 0;
}

int BG_LuaCloneTable(lua_State *L)
{
	if(lua_type(L, 2) != LUA_TTABLE)
	{
		lua_pushstring(L, "CloneTable called on non-table");
		lua_error(L);
	}

	lua_pushvalue(L, 2);
	// Base
	lua_createtable(L, 0, 0);
	// Base / T
	lua_pushnil(L);
	// Base / T / nil
	while(lua_next(L, 2) != 0)
	{
		// Base / T / key / value
		lua_pushvalue(L, -2);
		// Base / T / key / value / key
		lua_pushvalue(L, -2);
		// Base / T / key / value / key / value
		lua_rawset(L, -5);
		// Base / T / key / value
		lua_pop(L, 1);
		// Base / T / key
	}
	// Base / T
	return 1;
}

int BG_LuaValueDescription(lua_State *L)
{
	lua_pushvalue(L, 2);
	switch(lua_type(L, -1))
	{
	case LUA_TNONE:
	case LUA_TNIL:
		lua_pushstring(L, "[nil]");
		return 1;
	case LUA_TBOOLEAN:
		if(lua_toboolean(L, -1))
			lua_pushstring(L, "[true]");
		else
			lua_pushstring(L, "[false]");
		return 1;
	case LUA_TLIGHTUSERDATA:
		lua_pushstring(L, "[LUD]");
		return 1;
	case LUA_TTABLE:
		lua_pushstring(L, "[Table]");
		return 1;
	case LUA_TFUNCTION:
		lua_pushstring(L, "[Function]");
		return 1;
	case LUA_TUSERDATA:
		lua_pushstring(L, "[UD]");
		return 1;
	case LUA_TTHREAD:
		lua_pushstring(L, "[Thread]");
		return 1;
	case LUA_TSTRING:
		lua_pushstring(L, "[String:\"");
		lua_pushvalue(L, -2);
		lua_pushstring(L, "\"]");
		lua_concat(L, 3);
		return 1;
	case LUA_TNUMBER:
		lua_pushstring(L, "[Number:\"");
		lua_pushvalue(L, -2);
		lua_pushstring(L, "\"]");
		lua_concat(L, 3);
		return 1;
	};
	return 0;
}


int BG_LuaIRand(lua_State *L)
{
	int mn, mx, temp;

	mn = lua_tointeger(L, 2);
	mx = lua_tointeger(L, 3);

	// If range is backwards, invert it
	if(mn > mx)
	{
		temp = mn;
		mn = mx;
		mx = mn;
	}

	// Select randomly
	lua_pushnumber(L, mn + rand() % (mx - mn + 1));
	return 1;
}

// Return a string containing
int BG_VectorKey(lua_State *L)
{
	int i;

	char bytes[sizeof(lua_Number) * 3];
	lua_Number num;

	// Convert to bytes
	for(i=0;i<3;i++)
	{
		num = lua_tonumber(L, i+2);
		memcpy(bytes + sizeof(lua_Number)*i, &num, sizeof(lua_Number));
	}

	// Create a string from it
	lua_pushlstring(L, bytes, sizeof(lua_Number) * 3);

	return 1;
}


// Common syscall functions, except ENVIRONMENT, SETSPECIALPROPERTY, and SPECIALPROPERTYVALUE
int BG_LuaSysCall(lua_State *L, int callID)
{
	int r;
	const char *errMsg;

	switch(callID)
	{
	case LSYSCALL_ERROR:
		Com_Error(ERR_DROP, "%s", luaL_checkstring(L, 2));
	case LSYSCALL_PARSEVECTOR3:
		return BG_LuaParseVector3(L);
	case LSYSCALL_TRACE:
		return BG_LuaTrace(L);
	case LSYSCALL_VALUEDESCRIPTION:
		return BG_LuaValueDescription(L);
	case LSYSCALL_IRAND:
		return BG_LuaIRand(L);
	case LSYSCALL_ENUMERATETYPES:
		return BG_LuaEnumerateTypes(L);
	case LSYSCALL_CREATEREPLICATIONTABLE:
		return BG_LuaCreateReplicationTable(L);
	case LSYSCALL_BREAK:
		return 0;
	case LSYSCALL_GLOBALSTABLE:
		lua_pushvalue(L, LUA_GLOBALSINDEX);
		return 1;
	case LSYSCALL_VECTORKEY:
		return BG_VectorKey(L);
	case LSYSCALL_PRIMITIVETYPE:
		lua_pushnumber(L, lua_type(L, 2) + 1);
		return 1;
	case LSYSCALL_DATATYPEINFO:
		r = lua_tointeger(L, 2);
		if(r < 0 || r >= LUATYPE_COUNT)
		{
			lua_pushstring(L, "dataTypeInfo index was out of range");
			lua_error(L);
		}

		lua_pushnumber(L, luaDataTypeInfo[r].slotCount);
		return 1;

		return 1;
	case LSYSCALL_DECODEEVENTID:
		return BG_LuaDecodeEventID(L);
	case LSYSCALL_DECODEEVENTPARAMETERS:
		return BG_LuaDecodeEventParameters(L);
	default:
		Com_Error(ERR_DROP, "System call with unknown syscall ID: %i", callID);
		return 0;
	}
}

int BG_LuaReadSpecialProperty(lua_State *L, const void *pobj, const bg_luaSpecialProperty_t *p, int offset)
{
	const float *vec;
	const int *ip;
	int i;

	switch(p->readHandlingType)
	{
	case LFIELDTYPE_NOACCESS:
		lua_pushstring(L, "Attempted to read write-only property: ");
		lua_pushstring(L, p->name);
		lua_concat(L, 2);
		lua_error(L);
		break;

	case LFIELDTYPE_BITFIELD:
		ip = TCFIELD(int, pobj, offset);

		if((*ip) & p->bitMask)
			lua_pushboolean(L, 1);
		else
			lua_pushboolean(L, 0);

		// Return it
		return 1;

	case LFIELDTYPE_INTEGER:
		ip = TCFIELD(int, pobj, offset);

		lua_pushnumber(L, *ip);
		return 1;

	case LFIELDTYPE_VECTOR3:
		vec = TCFIELD(float, pobj, offset);

		BG_PushVector3v(L, vec);

		// Return the vector
		return 3;
	};

	return 0;
}

int BG_LuaWriteSpecialProperty(struct lua_State *L, int valueIndex, void *pobj, const bg_luaSpecialProperty_t *p, int offset)
{
	int i;
	int *ip;
	float *vec;

	switch(p->writeHandlingType)
	{
	case LFIELDTYPE_NOACCESS:
		lua_pushstring(L, "Attempted to write read-only property: ");
		lua_pushstring(L, p->name);
		lua_concat(L, 2);
		lua_error(L);
		break;

	case LFIELDTYPE_BITFIELD:
		ip = TFIELD(int, pobj, offset);

		if(lua_toboolean(L, valueIndex))
			*ip |= p->bitMask;
		else
			*ip &= ~p->bitMask;

		return 0;

	case LFIELDTYPE_INTEGER:
		ip = TFIELD(int, pobj, offset);

		*ip = lua_tointeger(L, valueIndex);
		return 0;

	case LFIELDTYPE_VECTOR3:
		vec = TFIELD(float, pobj, offset);

		for(i=0;i<3;i++)
		{
			lua_pushnumber(L, i+1);
			lua_gettable(L, valueIndex);
			if(lua_type(L, -1) == LUA_TNUMBER)
				vec[i] = (float)lua_tonumber(L, -1);
			lua_pop(L, 1);
		}

		return 0;
	};

	return 0;
}


void BG_PushTableFromInfo(struct lua_State *L, const char *info)
{
	int numProperties = 1;
	const char *scan;
	const char *key, *value;
	int keyLength, valueLength;
	int scanningKey = 1;

	keyLength = 0;
	value = NULL;

	if(info[0] == '\\')
		info++;

	scan = info;
	while(*scan)
	{
		if(*scan == '\\')
			numProperties++;
		scan++;
	}

	numProperties /= 2;

	lua_createtable(L, 0, numProperties);

	scan = key = info;
	while(1)
	{
		if(*scan == '\\' || *scan == '\0')
		{
			if(scanningKey)
			{
				keyLength = scan - key;
				value = scan+1;
				scanningKey = 0;
			}
			else
			{
				valueLength = scan - value;
				scanningKey = 1;

				lua_pushlstring(L, key, keyLength);
				lua_pushlstring(L, value, valueLength);
				lua_rawset(L, -3);

				key = scan+1;
			}

			if(*scan == '\0')
				break;
		}
		scan++;
	}
}


void BG_PushVector3(lua_State *L, float x, float y, float z)
{
	// Push parameters
	lua_pushnumber(L, x);
	lua_pushnumber(L, y);
	lua_pushnumber(L, z);
}

void BG_GetVector3v(lua_State *L, int index, float *v)
{
	int i;
	lua_pushvalue(L, index);
	for(i=0;i<3;i++)
	{
		lua_pushnumber(L, i+1);
		lua_gettable(L, -2);
		if(lua_type(L, -1) == LUA_TNUMBER)
			v[i] = (float)lua_tonumber(L, -1);
		else
			v[i] = 0;
		lua_pop(L, 1);
	}

	// Leave the vector on the stack
}

void BG_PushVector3v(lua_State *L, const float *v)
{
	BG_PushVector3(L, v[0], v[1], v[2]);
}


int BG_LuaSpecialPropertyValue(struct lua_State *L, const bg_luaSystemClass_t *classes, int numClasses)
{
	int catid, oid, spid;
	const bg_luaSystemClass_t *c;

	catid = lua_tointeger(L, 2);
	oid = lua_tointeger(L, 3);
	spid = lua_tointeger(L, 4);

	if(catid < 0 || catid >= numClasses)
		Com_Error(ERR_DROP, "SpecialPropertyValue called with bad CatID: %i", catid);

	c = classes + catid;

	if(spid < 0 || spid >= c->numSpecialProperties)
		Com_Error(ERR_DROP, "SpecialPropertyValue called on %s with bad SPID: %i", c->name, spid);

	return c->readProperty(L, oid, c->specialProperties + spid);
}

int BG_LuaSetSpecialProperty(struct lua_State *L, const bg_luaSystemClass_t *classes, int numClasses)
{
	int catid, oid, spid;
	const bg_luaSystemClass_t *c;

	catid = lua_tointeger(L, 2);
	oid = lua_tointeger(L, 3);
	spid = lua_tointeger(L, 4);

	if(catid < 0 || catid >= numClasses)
		Com_Error(ERR_DROP, "SetSpecialProperty called with bad CatID: %i", catid);

	c = classes + catid;

	if(spid < 0 || spid >= c->numSpecialProperties)
		Com_Error(ERR_DROP, "SetSpecialProperty called on %s with bad SPID: %i", c->name, spid);

	return c->writeProperty(L, oid, c->specialProperties + spid, 5);
}

#define ADD_CONSTANT(n)	\
	lua_pushnumber(L, n);	\
	lua_setfield(L, -2, #n)


void BG_LuaLoadStandardLibraries(struct lua_State *L)
{
	static const char *unsafeSymbols[] =
	{
		"dofile", "print", "load", "loadstring", "loadfile", "newproxy", "package", "module", "require", NULL
	};
	const char **sym;

	luaopen_base(L);
	luaopen_table(L);
	luaopen_string(L);
	luaopen_math(L);
	luaopen_debug(L);

	// Remove unsafe base functions from the global namespace
	sym = unsafeSymbols;
	while(*sym)
	{
		lua_pushnil(L);
		lua_setglobal(L, *sym);

		sym++;
	}

	// Create system constants table
	lua_createtable(L, 0, 0);

	ADD_CONSTANT(LREPTABLE_SLOT_OFFSET);
	ADD_CONSTANT(LREPTABLE_SLOT_BITS);

	ADD_CONSTANT(LREPTABLE_STAT_OFFSET);
	ADD_CONSTANT(LREPTABLE_STAT_BITS);

	ADD_CONSTANT(LREPTABLE_BITOFFS_OFFSET);
	ADD_CONSTANT(LREPTABLE_BITOFFS_BITS);

	ADD_CONSTANT(LREPTABLE_TYPE_OFFSET);
	ADD_CONSTANT(LREPTABLE_TYPE_BITS);

	ADD_CONSTANT(LFIELDTYPE_FLOAT);
	ADD_CONSTANT(LFIELDTYPE_VECTOR3);

	ADD_CONSTANT(LUATYPE_CLASSREF);
	ADD_CONSTANT(LUATYPE_OBJECTREF);
	ADD_CONSTANT(LUATYPE_INTEGER);
	ADD_CONSTANT(LUATYPE_STRING);
	ADD_CONSTANT(LUATYPE_VECTOR3);
	ADD_CONSTANT(LUATYPE_VECTOR4);
	ADD_CONSTANT(LUATYPE_FLOAT);
	ADD_CONSTANT(LUATYPE_SHORT);
	ADD_CONSTANT(LUATYPE_BYTE);

	ADD_CONSTANT(LUATYPE_NORMALIZED_ANGLE);
	ADD_CONSTANT(LUATYPE_NORMALIZED_ROTATION);

	ADD_CONSTANT(MAX_STATS);
	ADD_CONSTANT(MAX_GDATA);

	ADD_CONSTANT(PM_NORMAL);
	ADD_CONSTANT(PM_NOCLIP);
	ADD_CONSTANT(PM_SPECTATOR);
	ADD_CONSTANT(PM_DEAD);
	ADD_CONSTANT(PM_FREEZE);
	ADD_CONSTANT(PM_INTERMISSION);

	//ADD_CONSTANT(LREPEVENT_TYPE_VECTOR3)
	//ADD_CONSTANT(LREPEVENT_TYPE_VECTOR4)

	lua_setglobal(L, "SystemConstants");
}

void BG_LuaRegisterClasses(struct lua_State *L, const bg_luaSystemClass_t *classes, int numClasses)
{
	int i,j;
	const bg_luaSystemClass_t *c;

	lua_getglobal(L, "SystemConstants");
	// SC

	lua_createtable(L, 0, numClasses);
	// SC / classes

	for(i=0;i<numClasses;i++)
	{
		c = classes + i;

		lua_createtable(L, 0, 2);
		// classes / class

		lua_pushnumber(L, i);
		lua_setfield(L, -2, "nativeClassID");

		lua_createtable(L, 0, c->numSpecialProperties);
		// classes / class / properties

		for(j=0;j<c->numSpecialProperties;j++)
		{
			lua_createtable(L, 0, 1);
			// classes / class / properties / property
			lua_pushnumber(L, j);
			lua_setfield(L, -2, "nativePropertyID");

			if(c->specialProperties[j].className)
			{
				lua_pushstring(L, c->specialProperties[j].className);
				lua_setfield(L, -2, "constructor");
			}

			lua_setfield(L, -2, c->specialProperties[j].name);
		}

		// classes / class / properties
		lua_setfield(L, -2, "properties");
		// classes / class

		lua_setfield(L, -2, c->name);
	}

	lua_setfield(L, -2, "NATIVE_CLASSES");
	lua_pop(L, 1);
}

int BG_LuaEnumerateTypes(struct lua_State *L)
{
	int i;

	lua_createtable(L, 0, LUATYPE_COUNT);
	// Rep / Types

	for(i=0;i<LUATYPE_COUNT;i++)
	{
		lua_pushstring(L, luaDataTypeInfo[i].name);
		lua_pushnumber(L, i);
		lua_settable(L, -3);
	}

	return 1;
}

typedef struct
{
	int bitsUsed;
} repTableSlot_t;

typedef struct
{
	repTableSlot_t *slots;
	int numUsedSlots;
	int numSlots;
} repTable_t;

enum
{
	LUART_ERROR_NOSLOTS	= -1,
	LUART_ERROR_BADTYPE = -2,
};

// Initializes a replication table
static void RT_Init(repTable_t *rt)
{
	int i;

	for(i=0;i<rt->numSlots;i++)
		rt->slots[i].bitsUsed = 0;
	rt->numUsedSlots = 0;
}

static int RT_AddNew(repTable_t *rt, int type, int tableIndex)
{
	int numSlots, numBits, i;
	int slotIndex, bitOffset;

	if(type >= LUATYPE_COUNT)
		return LUART_ERROR_BADTYPE;

	numSlots = luaDataTypeInfo[type].slotCount;

	if(numSlots > 0)
	{
		// Wide data type needs full slots
		if(rt->numUsedSlots + numSlots > rt->numSlots)
			return LUART_ERROR_NOSLOTS;
		bitOffset = 0;
		slotIndex = rt->numUsedSlots;

		// Fill all bits on new slots
		while(numSlots--)
			rt->slots[rt->numUsedSlots++].bitsUsed = 32;
	}
	else
	{
		// Narrow data types fit within existing slots
		numBits = -numSlots;
		slotIndex = -1;
		bitOffset = 0;

		for(i=0;i<rt->numUsedSlots;i++)
		{
			if(rt->slots[i].bitsUsed + numBits <= 32)
			{
				bitOffset = rt->slots[i].bitsUsed;
				slotIndex = i;
				rt->slots[i].bitsUsed += numBits;
			}
		}

		// Was it able to fit?
		if(slotIndex == -1)
		{
			// No, need a new slot
			if(rt->numUsedSlots == rt->numSlots)
				return LUART_ERROR_NOSLOTS;
			rt->slots[rt->numUsedSlots].bitsUsed = numBits;
			slotIndex = rt->numUsedSlots++;
			bitOffset = 0;
		}
	}

	// Return encoded rep table offset
	return (slotIndex << LREPTABLE_SLOT_OFFSET) | (tableIndex << LREPTABLE_STAT_OFFSET) | (bitOffset << LREPTABLE_BITOFFS_OFFSET) | (type << LREPTABLE_TYPE_OFFSET);
}

int BG_LuaCreateReplicationTable(struct lua_State *L)
{
	int i, rtiType, statFlag, rtSlot;

	repTableSlot_t statSlots[MAX_STATS];
	repTableSlot_t gdataSlots[MAX_GDATA];

	repTable_t statTable;
	repTable_t gdataTable;

	statTable.numSlots = MAX_STATS;
	statTable.slots = statSlots;
	gdataTable.numSlots = MAX_GDATA;
	gdataTable.slots = gdataSlots;

	RT_Init(&statTable);
	RT_Init(&gdataTable);

	// Process all replicated stats
	i = 1;

	// Push the replication properties to the top of the stack
	lua_pushvalue(L, 2);

	// RTI

	lua_createtable(L, 0, 0);
	// RTI / RT
	lua_createtable(L, 0, 0);
	// RTI / RT / RTC
	while(1)
	{
		lua_pushnumber(L, i);
		// RTI / RT / RTC / i
		lua_gettable(L, -4);
		// RTI / RT / RTC / RTI[i]

		if(lua_isnil(L, -1))
		{
			lua_pop(L, 1);
			// RTI / RT / RTC
			break;
		}

		// Name, Type [, stat flag]

		// Get the name to use as an index
		lua_pushnumber(L, 1);
		// RTI / RT / RTC / RTI[i] / 1
		lua_gettable(L, -2);
		// RTI / RT / RTC / RTI[i] / RTI[i].name

		// Get the type
		lua_pushnumber(L, 2);
		// RTI / RT / RTC / RTI[i] / RTI[i].name / 2
		lua_gettable(L, -3);
		// RTI / RT / RTC / RTI[i] / RTI[i].name / RTI[i].type
		rtiType = lua_tointeger(L, -1);

		lua_pop(L, 1);
		// RTI / RT / RTC / RTI[i] / RTI[i].name
		// Get the stat flag
		lua_pushstring(L, "stat");
		// RTI / RT / RTC / RTI[i] / RTI[i].name / 3
		lua_gettable(L, -3);
		// RTI / RT / RTC / RTI[i] / RTI[i].name / RTI[i].statFlag

		statFlag = lua_toboolean(L, -1);
		lua_pop(L, 1);
		// RTI / RT / RTC / RTI[i] / RTI[i].name

		if(statFlag)
			rtSlot = RT_AddNew(&statTable, rtiType, 1);
		else
			rtSlot = RT_AddNew(&gdataTable, rtiType, 0);

		if(rtSlot == LUART_ERROR_NOSLOTS)
		{
			lua_pushstring(L, "Replication table is too large");
			lua_error(L);
		}
		if(rtSlot == LUART_ERROR_BADTYPE)
		{
			lua_pushstring(L, "Bad type set for replicated property");
			lua_error(L);
		}

		lua_pushvalue(L, -1);
		// RTI / RT / RTC / RTI[i] / RTI[i].name / RTI[i].name

		lua_pushnumber(L, rtSlot);
		// RTI / RT / RTC / RTI[i] / RTI[i].name / RTI[i].name / rtSlot

		// Set this in the replication table
		lua_settable(L, -6);
		// RTI / RT / RTC / RTI[i] / RTI[i].name

		if(luaDataTypeInfo[rtiType].cl)
		{
			lua_getglobal(L, luaDataTypeInfo[rtiType].cl);
			// RTI / RT / RTC / RTI[i] / RTI[i].name / Class
			lua_settable(L, -4);
			// RTI / RT / RTC / RTI[i]
		}
		else
		{
			lua_pop(L, 1);
			// RTI / RT / RTC / RTI[i]
		}

		lua_pop(L, 1);
		// RTI / RT / RTC

		i++;
	}

	// RTI / RT / RTC
	lua_pushnumber(L, gdataTable.numUsedSlots);
	// RTI / RT / RTC / numSlots

	return 3;
}

void BG_DecodeGData(struct lua_State *L, int base, int *slotIndexP, int *tableIndexP, int *bitOffsetP, int *typeP)
{
	int slotIndex, tableIndex, bitOffset, type, ceiling, typeWidth;

	*slotIndexP = slotIndex = (base >> LREPTABLE_SLOT_OFFSET) & BIT_MASK(LREPTABLE_SLOT_BITS);
	*tableIndexP = tableIndex = (base >> LREPTABLE_STAT_OFFSET) & BIT_MASK(LREPTABLE_STAT_BITS);
	*bitOffsetP = bitOffset = (base >> LREPTABLE_BITOFFS_OFFSET) & BIT_MASK(LREPTABLE_BITOFFS_BITS);
	*typeP = type = (base >> LREPTABLE_TYPE_OFFSET) & BIT_MASK(LREPTABLE_TYPE_BITS);

	if(type >= LUATYPE_COUNT)
	{
		lua_pushstring(L, "Lua type identifier > LUATYPE_COUNT");
		lua_error(L);
	}

	// Check slot width
	typeWidth = luaDataTypeInfo[type].slotCount;
	if(typeWidth < 0)
		typeWidth = 1;

	if(tableIndex == 0)
		ceiling = MAX_GDATA;
	else if(tableIndex == 1)
		ceiling = MAX_STATS;

	if(slotIndex + typeWidth > MAX_STATS)
	{
		lua_pushstring(L, "GData slot overflow");
		lua_error(L);
	}
}


int BG_LuaGDataPropertyValue(struct lua_State *L, data32u_t *gdata, int *stats)
{
	int gdataSlot = lua_tointeger(L, 3);
	int slot, table, bit, type, i;
	short s;
	char c;

	data32u_t *gdt;

	BG_DecodeGData(L, gdataSlot, &slot, &table, &bit, &type);

	if(table)
		gdt = (data32u_t *)stats + slot;
	else
		gdt = gdata + slot;

	switch(type)
	{
	case LUATYPE_INTEGER:
		lua_pushnumber(L, gdt->i);
		return 1;
	case LUATYPE_VECTOR3:
		// Push the constructor
		lua_getglobal(L, "vector3");
		for(i=0;i<3;i++)
			lua_pushnumber(L, gdt[i].f);
		lua_call(L, 3, 1);
		return 1;
	case LUATYPE_VECTOR4:
		// Push the constructor
		lua_getglobal(L, "vector4");
		for(i=0;i<4;i++)
			lua_pushnumber(L, gdt[i].f);
		lua_call(L, 4, 1);
		return 1;
	case LUATYPE_FLOAT:
		lua_pushnumber(L, gdt->f);
		return 1;
	case LUATYPE_SHORT:
		s = (short)(gdt->i >> bit);
		lua_pushnumber(L, s);
		return 1;
	case LUATYPE_BYTE:
		c = (char)(gdt->i >> bit);
		lua_pushnumber(L, c);
		return 1;

	case LUATYPE_NORMALIZED_ANGLE:
	case LUATYPE_NORMALIZED_ROTATION:
	default:
		break;
	};

	return 0;
}

int BG_LuaSetGDataProperty(struct lua_State *L, data32u_t *gdata, int *stats)
{
	int gdataSlot = lua_tointeger(L, 3);
	int slot, table, bit, type, i;
	short s;
	char c;

	data32u_t *gdt;

	BG_DecodeGData(L, gdataSlot, &slot, &table, &bit, &type);

	if(table)
		gdt = (data32u_t *)stats + slot;
	else
		gdt = gdata + slot;

	switch(type)
	{
	case LUATYPE_INTEGER:
		gdt->i = lua_tointeger(L, 4);
		return 0;
	case LUATYPE_VECTOR3:
		// Push the constructor
		for(i=0;i<3;i++)
		{
			lua_pushnumber(L, i+1);
			lua_gettable(L, 4);
			gdt[i].f = lua_tonumber(L, i);
			lua_pop(L, 1);
		}
		return 0;
	case LUATYPE_VECTOR4:
		// Push the constructor
		for(i=0;i<4;i++)
		{
			lua_pushnumber(L, i+1);
			lua_gettable(L, 4);
			gdt[i].f = lua_tonumber(L, i);
			lua_pop(L, 1);
		}
		return 0;
	case LUATYPE_FLOAT:
		gdt->f = lua_tonumber(L, 4);
		return 0;
	case LUATYPE_SHORT:
		i = (lua_tointeger(L, 4) << bit) & BIT_MASK(16);
		gdt->i = (gdt->i & ((~BIT_MASK(16)) << bit)) | i;
		return 0;
	case LUATYPE_BYTE:
		i = (lua_tointeger(L, 4) << bit) & BIT_MASK(8);
		gdt->i = (gdt->i & ((~BIT_MASK(8)) << bit)) | i;
		return 0;

	case LUATYPE_NORMALIZED_ANGLE:
	case LUATYPE_NORMALIZED_ROTATION:
	default:
		break;
	};
	return 0;
}

// Encoding
static void PushEncodedBytes(struct lua_State *L, unsigned char *bytes, int numBytes, char **result, int *limit)
{
	int i;
	char *out = *result;

	if(*limit < numBytes * 2)
	{
		lua_pushstring(L, "Oversized event string");
		lua_error(L);
	}

	while(numBytes)
	{
		// Encode as ASCII VLC
		if(bytes[0] < 32)
		{
			*out++ = '0' + bytes[0];
			(*limit) --;
		}
		else
		{
			*out++ = '0' + 32 + (bytes[0] & 0x1f);
			*out++ = '0' + (bytes[0] >> 5);
			(*limit) -= 2;
		}

		numBytes--;
		bytes++;
	}
	*out = '\0';

	*result = out;
}

static void GetEncodedBytes(const char **inp, unsigned char *bytes, int numBytes)
{
	int i;
	const char *in = *inp;
	int decoded;

	while(numBytes)
	{
		if(*in == '\0')
			*bytes = 0;
		else
		{
			decoded = (*in) - '0';
			if(decoded & 0x20)
			{
				// VLC code
				decoded &= 0x1f;
				in++;
				if(*in != '\0')
					decoded |= ((*in) - '0') << 5;
			}
			*bytes = (unsigned char)decoded;

			in++;
		}

		bytes++;
		numBytes--;
	}

	*inp = in;
}


static void PushEncodedLong(struct lua_State *L, long l, char **result, int *limit)
{
	unsigned char bytes[4];

	bytes[0] = l & 0xff;
	bytes[1] = (l >> 8) & 0xff;
	bytes[2] = (l >> 16) & 0xff;
	bytes[3] = (l >> 24) & 0xff;

	PushEncodedBytes(L, bytes, 4, result, limit);
}

static long GetEncodedLong(const char **inp)
{
	unsigned char bytes[4];

	GetEncodedBytes(inp, bytes, 4);

	return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}


static void PushEncodedFloat(struct lua_State *L, float f, char **result, int *limit)
{
	union
	{
		long l;
		float f;
	} u;

	u.f = f;

	PushEncodedLong(L, u.l, result, limit);
}

static float GetEncodedFloat(const char **inp)
{
	union
	{
		long l;
		float f;
	} u;

	u.l = GetEncodedLong(inp);

	return u.f;
}

static void PushEncodedCompact(struct lua_State *L, long l, char **result, int *limit)
{
	unsigned char bytes[5];
	int nBytes = 0;

	if(l)
	{
		while(l)
		{
			bytes[nBytes] = l & 0x7f;
			if(bytes[nBytes] != l)
			{
				// Need more
				bytes[nBytes] |= 0x80;
			}
			l = (l >> 7) & 0x01ffffff;
			nBytes++;
		}
	}
	else
	{
		bytes[0] = 0;
		nBytes = 1;
	}

	PushEncodedBytes(L, bytes, nBytes, result, limit);
}


static long GetEncodedCompact(const char **inp)
{
	unsigned char b;
	long result = 0;

	while(1)
	{
		GetEncodedBytes(inp, &b, 1);
		result = (result << 7) | (b & 0x7f);
		if((b & 0x80) == 0)
			break;
	}

	return result;
}

void BG_LuaBuildEventCommand(struct lua_State *L, char *result, int limit)
{
	int hookID;
	int paramNum = 0;
	int paramType;
	int i;
	float f;
	const char *str;
	size_t len;

	strcpy(result, "ge ");
	limit -= 4;		// 1 extra for the terminating null
	result += 3;

	hookID = lua_tointeger(L, 2);

	PushEncodedCompact(L, hookID, &result, &limit);

	while(1)
	{
		// Get the parameter type
		lua_pushinteger(L, paramNum+1);
		lua_gettable(L, 3);

		if(lua_isnil(L, -1))
		{
			lua_pop(L, 1);
			return;
		}

		// See what parameter type this is
		paramType = lua_tointeger(L, -1);
		lua_pop(L, 1);

		switch(paramType)
		{
		case LUATYPE_OBJECTREF:
			lua_pushvalue(L, paramNum+5);

			// P
			if(lua_isnil(L, -1))
			{
				i = -1;
				lua_pop(L, 1);
			}
			else
			{
				lua_getfield(L, -1, "_META");
				// P / P._META
				lua_getfield(L, -1, "nativeObjectID");
				// P / P._META / P._META.nativeObjectID
				i = lua_tointeger(L, -1);
				lua_pop(L, 3);
			}

			PushEncodedCompact(L, i, &result, &limit);

			break;
		case LUATYPE_INTEGER:
			i = lua_tointeger(L, paramNum+5);
			PushEncodedCompact(L, i, &result, &limit);
			break;
		case LUATYPE_STRING:
			str = lua_tolstring(L, paramNum+5, &len);
			if(str)
			{
				// Write Pascal string
				PushEncodedCompact(L, (long)len, &result, &limit);
				PushEncodedBytes(L, (unsigned char *)str, len, &result, &limit);
			}
			else
				PushEncodedCompact(L, 0, &result, &limit);
			break;
		};

		paramNum++;
	}
}


int BG_LuaDecodeEventID(lua_State *L)
{
	const char *str = lua_tostring(L, 2);

	if(!str)
		return 0;

	lua_pushinteger(L, GetEncodedCompact(&str) + 1);
	return 1;
}

int BG_LuaDecodeEventParameters(lua_State *L)
{
	const char *str = lua_tostring(L, 2);
	int paramNum = 0;
	int paramType;
	int i;

	size_t l;
	unsigned char tempStr[64];
	void *tempStrPtr = tempStr;
	int chunksize = sizeof(tempStr);

	if(!str)
		return 0;

	GetEncodedCompact(&str);	// Skip the event ID

	// See how much stack we'll need to push everything
	lua_checkstack(L, lua_objlen(L, 3) + 6);

	while(1)
	{
		// Get parameter type
		lua_pushinteger(L, paramNum+1);
		lua_gettable(L, 3);

		if(lua_isnil(L, -1))
		{
			lua_pop(L, 1);
			break;
		}

		paramType = lua_tointeger(L, -1);
		lua_pop(L, 1);

		switch(paramType)
		{
		case LUATYPE_OBJECTREF:
			// Get the object with this ID
			i = GetEncodedCompact(&str);
			lua_getglobal(L, "worldObjectFromID");
			lua_pushinteger(L, i);
			lua_call(L, 1, 1);
			break;
		case LUATYPE_INTEGER:
			i = GetEncodedCompact(&str);
			lua_pushinteger(L, i);
			break;
		case LUATYPE_STRING:
			l = GetEncodedCompact(&str);

			lua_pushstring(L, "");
			while(l > chunksize)
			{
				GetEncodedBytes(&str, tempStrPtr, chunksize);
				lua_pushlstring(L, tempStrPtr, chunksize);
				lua_concat(L, 2);	// This is a waste, but easier than messing with stack resizes if we get a malicious packet
				l -= chunksize;
			}
			GetEncodedBytes(&str, tempStrPtr, l);
			lua_pushlstring(L, tempStrPtr, l);
			lua_concat(L, 2);
			break;
		};

		paramNum++;
	}

	return paramNum;
}